题解 P3043 【[USACO12JAN]牛联盟Bovine Alliance】

$Description$

给出$n$个点$m$条边的图,每条边找一个配对的点,要求边$(u,v)$配对的点是$u$或$v,$且每个点最多只能被一条边配对,求不同方案数

$ps:$题面的翻译不太准确,上面的翻译来自讨论,并稍加补充

$Solution$

先给出一组$hack$数据

1
2
3
4
5
6
7
8
9
10
input:
8 6
1 2
1 3
2 3
1 4
2 4
3 4
ouput:
0

有好几篇题解会被$hack,$原因是没有判在一个联通块中边数大于点数的情况

我们先$dfs$找出每个联通块,并算出这个联通块中的点数$po$与边数$ed,$然后分类讨论,最后利用乘法原理乘起来

$1.po>ed:$由于是联通块,所以只能是$po=ed+1$,即是一棵树。这种情况下我们在$po$个点中找$ed$个点去匹配,无论怎么选都是合法的,所以贡献即是$C_{po}^{ed}=C_{po}^{po-1}=po$

$2.po==ed:$即一个环的情况,只有顺时针选和逆时针选两种情况,所以贡献即是$2$

$3.po<ed:$这种情况下无论怎么选都不合法,所以贡献为$0$

$Code$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <bits/stdc++.h>
#define int long long
#define inf 0x3f3f3f3f
#define mod 1000000007
#define N 200700
using namespace std;
struct edge{
int to,next;
}e[N];
inline int read(){
int x=0,w=0;char ch=getchar();
while (!isdigit(ch))w|=ch=='-',ch=getchar();
while (isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return w?-x:x;
}
int cnt,head[N],vis[N],po,ed,ans=1;
inline void add(int u,int v){
e[++cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void dfs(int u){
vis[u]=1;
++po;
for (int i=head[u];i;i=e[i].next,++ed){
int v=e[i].to;
if (!vis[v])
dfs(v);
}
}
signed main(){
int n=read(),m=read();
for (int i=1;i<=m;++i){
int u=read(),v=read();
add(u,v);add(v,u);
}
for (int i=1;i<=n;++i)
if (!vis[i]){
po=ed=0;
dfs(i);
ed/=2;
if (po>ed)ans=ans*po%mod;
if (po==ed)ans=ans*2%mod;
if (po<ed)ans=0;
}
printf("%d\n",ans);
return 0;
}